<template>
    <ul class="bc-tree">
      <bc-tree-item
        v-for="(node, index) in data" 
        :key="index" :data="node"
        :showCheckbox="showCheckbox"
      ></bc-tree-item>
    </ul>
</template>

<script>
import BcTreeItem from './treeItem.vue'
export default {
    name: 'BcTree',
    components: {BcTreeItem},
    props: {
        data: {
            type: Array,
            default: () => []
        },
        childrenKey: {
            type: String,
            default: 'children'
        },
        showCheckbox: Boolean
    },
    data() {
        return {
            stateTree: this.data,
            // 节点数组，nodeKey保存的是在数组中的位置
            // { node: node, nodeKey: node.nodeKey, 
            // parent: parent.nodeKey, children: [child.nodeKey] }
            flatState: []
        }
    },
    // 用来在异步拿到数据后更新节点状态
    watch: {
        data: {
            deep: true,
            handler () {
                this.stateTree = this.data;
                this.flatState = this.compileFlatState();
                this.rebuildTree();
            }
        }
    },
    methods: {
        // 把多个树转化为节点数组，节点的位置保存在节点信息里
        compileFlatState () {
            let keyCounter = 0;
            let childrenKey = 'children';
            const flatTree = [];
            function flattenChildren(node, parent) {
                node.nodeKey = keyCounter++;
                flatTree[node.nodeKey] = { node: node, nodeKey: node.nodeKey };
                if (typeof parent != 'undefined') {
                    flatTree[node.nodeKey].parent = parent.nodeKey;
                    flatTree[parent.nodeKey][childrenKey].push(node.nodeKey);
                }
                if (node[childrenKey]) {
                    flatTree[node.nodeKey][childrenKey] = [];
                    node[childrenKey].forEach(child => flattenChildren(child, node));
                }
            }
            this.stateTree.forEach(rootNode => {
                flattenChildren(rootNode);
            })
            return flatTree;
        },
        // 勾选节点更新父节点的checked
        updateTreeUp(nodeKey) {
            const parentKey = this.flatState[nodeKey].parent;
            if (typeof parentKey == 'undefined') return;

            const node = this.flatState[nodeKey].node;
            const parent = this.flatState[parentKey].node;
            if (node.checked == parent.checked && node.indeterminate == parent.indeterminate) return;
            // 勾选节点
            if (node.checked == true) {
                // 父节点下每个子节点都被选中，或者每个子节点都是disabled，就选中父
                this.$set(parent, 'checked', parent[this.childrenKey].every(node => node.checked || node.disabled !== undefined ));
                // 之后把父节点的indeterminate设为!parent.checked
                this.$set(parent, 'indeterminate', !parent.checked);
            } else {
                // 取消勾选，把父节点去勾选
                this.$set(parent, 'checked', false);
                // 父节点下有任意一个子节点被选中，或者有一个子节点是indeterminate，就选把父设为indeterminate
                this.$set(parent, 'indeterminate', parent[this.childrenKey].some(node => node.checked || node.indeterminate)); 
            }
            // 往上递归到最上层
            this.updateTreeUp(parentKey);
        },
        // 勾选节点更新子孙节点的checked
        updateTreeDown(node, changes = {}) {
            for (let key in changes) {
                // 节点为disabled，设置checked
                if( key === 'checked' && node.disabled ){
                    this.$set(node, key, node.checked);
                }else{
                // 节点不为disabled，设置checked或indeterminate
                    this.$set(node, key, changes[key]);
                }
            }
            if (node[this.childrenKey]) {
                node[this.childrenKey].forEach(child => {
                    this.updateTreeDown(child, changes);
                });
            }
        },
        rebuildTree () { // only called when `data` prop changes
            const checkedNodes = this.getCheckedNodes();
            checkedNodes.forEach(node => {
                this.updateTreeDown(node, {checked: true});
                const parentKey = this.flatState[node.nodeKey].parent;
                if (!parentKey && parentKey !== 0) return;
                const parent = this.flatState[parentKey].node;
                const childHasCheckSetter = typeof node.checked != 'undefined' && node.checked;
                if (childHasCheckSetter && parent.checked != node.checked) {
                    this.updateTreeUp(node.nodeKey); // update tree upwards
                }
            });
        },
        handleCheck({ checked, nodeKey }) {
            const node = this.flatState[nodeKey].node;
            this.$set(node, 'checked', checked);
            this.$set(node, 'indeterminate', false);

            this.updateTreeUp(nodeKey);
            this.updateTreeDown(node, {checked, indeterminate: false}); // reset `indeterminate` when going down
            
            this.$emit('on-check-change', this.getCheckedNodes(), node);
        },
        getCheckedNodes () {
            /* public API */
            // 此处写法虽简洁，但用了两次遍历，若放在一个循环里实现会不会更优？
            return this.flatState.filter(obj => obj.node.checked).map(obj => obj.node);
        },
        getCheckedAndIndeterminateNodes () {
            /* public API */
            return this.flatState.filter(obj => (obj.node.checked || obj.node.indeterminate)).map(obj => obj.node);
        },
    },
    created() {
        this.flatState = this.compileFlatState();
        this.rebuildTree();
    },
    mounted() {
        this.$on('on-check', this.handleCheck);
        // 注意此处on与emit事件名不能一样，不然会有死循环
        this.$on('toggle-expand', node => this.$emit('on-toggle-expand', node));
        this.$on('node-click', node => this.$emit('on-node-click', node));
    }
}
</script>